/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.jmeter.protocol.smtp.sampler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.mail.AuthenticationFailedException;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.AddressException;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.protocol.smtp.sampler.gui.SecuritySettingsPanel;
import org.apache.jmeter.protocol.smtp.sampler.protocol.SendMailCommand;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.CollectionProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sampler-Class for JMeter - builds, starts and interprets the results of the
* sampler. Has to implement some standard-methods for JMeter in order to be
* integrated in the framework. All getter/setter methods just deliver/set
* values from/to the sampler, not from/to the message-object. Therefore, all
* these methods are also present in class SendMailCommand.
*/
public class SmtpSampler extends AbstractSampler {
private static final long serialVersionUID = 1L;
private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<>(
Arrays.asList("org.apache.jmeter.config.gui.SimpleConfigGui"));
private static final Logger log = LoggerFactory.getLogger(SmtpSampler.class);
//+JMX file attribute names - do not change any values!
public static final String SERVER = "SMTPSampler.server"; // $NON-NLS-1$
public static final String SERVER_PORT = "SMTPSampler.serverPort"; // $NON-NLS-1$
public static final String SERVER_TIMEOUT = "SMTPSampler.serverTimeout"; // $NON-NLS-1$
public static final String SERVER_CONNECTION_TIMEOUT = "SMTPSampler.serverConnectionTimeout"; // $NON-NLS-1$
public static final String USE_AUTH = "SMTPSampler.useAuth"; // $NON-NLS-1$
public static final String USERNAME = "SMTPSampler.username"; // $NON-NLS-1$
public static final String PASSWORD = "SMTPSampler.password"; // $NON-NLS-1$ NOSONAR not a hardcoded password
public static final String MAIL_FROM = "SMTPSampler.mailFrom"; // $NON-NLS-1$
public static final String MAIL_REPLYTO = "SMTPSampler.replyTo"; // $NON-NLS-1$
public static final String RECEIVER_TO = "SMTPSampler.receiverTo"; // $NON-NLS-1$
public static final String RECEIVER_CC = "SMTPSampler.receiverCC"; // $NON-NLS-1$
public static final String RECEIVER_BCC = "SMTPSampler.receiverBCC"; // $NON-NLS-1$
public static final String SUBJECT = "SMTPSampler.subject"; // $NON-NLS-1$
public static final String SUPPRESS_SUBJECT = "SMTPSampler.suppressSubject"; // $NON-NLS-1$
public static final String MESSAGE = "SMTPSampler.message"; // $NON-NLS-1$
public static final String PLAIN_BODY = "SMTPSampler.plainBody"; // $NON-NLS-1$
public static final String INCLUDE_TIMESTAMP = "SMTPSampler.include_timestamp"; // $NON-NLS-1$
public static final String ATTACH_FILE = "SMTPSampler.attachFile"; // $NON-NLS-1$
public static final String MESSAGE_SIZE_STATS = "SMTPSampler.messageSizeStatistics"; // $NON-NLS-1$
public static final String HEADER_FIELDS = "SMTPSampler.headerFields"; // $NON-NLS-1$
public static final String USE_EML = "SMTPSampler.use_eml"; // $NON-NLS-1$
public static final String EML_MESSAGE_TO_SEND = "SMTPSampler.emlMessageToSend"; // $NON-NLS-1$
public static final String ENABLE_DEBUG = "SMTPSampler.enableDebug"; // $NON-NLS-1$
// Used to separate attachment file names in JMX fields - do not change!
public static final String FILENAME_SEPARATOR = ";";
//-JMX file attribute names
public SmtpSampler() {
}
/**
* Performs the sample, and returns the result
*
* @param e Standard-method-header from JMeter
* @return Result of the sample
* @see org.apache.jmeter.samplers.Sampler#sample(org.apache.jmeter.samplers.Entry)
*/
@Override
public SampleResult sample(Entry e) {
SendMailCommand sendMailCmd;
Message message;
SampleResult result = createSampleResult();
try {
sendMailCmd = createSendMailCommandFromProperties();
message = sendMailCmd.prepareMessage();
result.setBytes(calculateMessageSize(message));
} catch (Exception ex) {
log.warn("Error while preparing message", ex);
result.setResponseCode("500");
result.setResponseMessage(ex.toString());
return result;
}
// Set up the sample result details
result.setDataType(SampleResult.TEXT);
try {
result.setRequestHeaders(getRequestHeaders(message));
result.setSamplerData(getSamplerData(message));
} catch (MessagingException | IOException ex) {
result.setSamplerData("Error occurred trying to save request info: " + ex);
log.warn("Error occurred trying to save request info", ex);
}
// Perform the sampling
result.sampleStart();
boolean isSuccessful = executeMessage(result, sendMailCmd, message);
result.sampleEnd();
try {
result.setResponseData(processSampler(message));
} catch (IOException | MessagingException ex) {
log.warn("Failed to set result response data", ex);
}
result.setSuccessful(isSuccessful);
return result;
}
private SampleResult createSampleResult() {
SampleResult result = new SampleResult();
result.setSampleLabel(getName());
return result;
}
private boolean executeMessage(SampleResult result, SendMailCommand sendMailCmd, Message message) {
boolean didSampleSucceed = false;
try {
sendMailCmd.execute(message);
result.setResponseCodeOK();
result.setResponseMessage(
"Message successfully sent!\n" + sendMailCmd.getServerResponse());
didSampleSucceed = true;
} catch (AuthenticationFailedException afex) {
log.warn("", afex);
result.setResponseCode("500");
result.setResponseMessage(
"AuthenticationFailedException: authentication failed - wrong username / password!\n"
+ afex);
} catch (Exception ex) {
log.warn("", ex);
result.setResponseCode("500");
result.setResponseMessage(ex.getMessage());
}
return didSampleSucceed;
}
private long calculateMessageSize(Message message) throws IOException, MessagingException {
if (getPropertyAsBoolean(MESSAGE_SIZE_STATS)) {
// calculate message size
CountingOutputStream cs = new CountingOutputStream(new NullOutputStream());
message.writeTo(cs);
return cs.getByteCount();
} else {
return -1L;
}
}
private byte[] processSampler(Message message) throws IOException, MessagingException {
// process the sampler result
try (InputStream is = message.getInputStream()) {
return IOUtils.toByteArray(is);
}
}
private List<File> getAttachmentFiles() {
final String[] attachments = getPropertyAsString(ATTACH_FILE).split(FILENAME_SEPARATOR);
return Arrays.stream(attachments) // NOSONAR No need to close
.map(this::attachmentToFile)
.collect(Collectors.toList());
}
private File attachmentToFile(String attachment) { // NOSONAR False positive saying not used
File file = new File(attachment);
if (!file.isAbsolute() && !file.exists()) {
if(log.isDebugEnabled()) {
log.debug("loading file with relative path: " + attachment);
}
file = new File(FileServer.getFileServer().getBaseDir(), attachment);
if(log.isDebugEnabled()) {
log.debug("file path set to: " + attachment);
}
}
return file;
}
private String calculateSubject() {
if (getPropertyAsBoolean(SUPPRESS_SUBJECT)) {
return null;
} else {
String subject = getPropertyAsString(SUBJECT);
if (getPropertyAsBoolean(INCLUDE_TIMESTAMP)) {
subject = subject
+ " <<< current timestamp: "
+ System.currentTimeMillis()
+ " >>>";
}
return subject;
}
}
private SendMailCommand createSendMailCommandFromProperties() throws AddressException {
SendMailCommand sendMailCmd = new SendMailCommand();
sendMailCmd.setSmtpServer(getPropertyAsString(SmtpSampler.SERVER));
sendMailCmd.setSmtpPort(getPropertyAsString(SmtpSampler.SERVER_PORT));
sendMailCmd.setConnectionTimeOut(getPropertyAsString(SmtpSampler.SERVER_CONNECTION_TIMEOUT));
sendMailCmd.setTimeOut(getPropertyAsString(SmtpSampler.SERVER_TIMEOUT));
sendMailCmd.setUseSSL(getPropertyAsBoolean(SecuritySettingsPanel.USE_SSL));
sendMailCmd.setUseStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.USE_STARTTLS));
sendMailCmd.setTrustAllCerts(getPropertyAsBoolean(SecuritySettingsPanel.SSL_TRUST_ALL_CERTS));
sendMailCmd.setEnforceStartTLS(getPropertyAsBoolean(SecuritySettingsPanel.ENFORCE_STARTTLS));
sendMailCmd.setUseAuthentication(getPropertyAsBoolean(USE_AUTH));
sendMailCmd.setUsername(getPropertyAsString(USERNAME));
sendMailCmd.setPassword(getPropertyAsString(PASSWORD));
sendMailCmd.setUseLocalTrustStore(getPropertyAsBoolean(SecuritySettingsPanel.USE_LOCAL_TRUSTSTORE));
sendMailCmd.setTrustStoreToUse(getPropertyAsString(SecuritySettingsPanel.TRUSTSTORE_TO_USE));
sendMailCmd.setEmlMessage(getPropertyAsString(EML_MESSAGE_TO_SEND));
sendMailCmd.setUseEmlMessage(getPropertyAsBoolean(USE_EML));
if (!getPropertyAsBoolean(USE_EML)) {
// if we are not sending an .eml file
sendMailCmd.setMailBody(getPropertyAsString(MESSAGE));
sendMailCmd.setPlainBody(getPropertyAsBoolean(PLAIN_BODY));
getAttachmentFiles().forEach(sendMailCmd::addAttachment);
}
sendMailCmd.setEnableDebug(getPropertyAsBoolean(ENABLE_DEBUG));
if (getPropertyAsString(MAIL_FROM).matches(".*@.*")) {
sendMailCmd.setSender(getPropertyAsString(MAIL_FROM));
}
// Process address lists
sendMailCmd.setReceiverTo(getPropAsAddresses(SmtpSampler.RECEIVER_TO));
sendMailCmd.setReceiverCC(getPropAsAddresses(SmtpSampler.RECEIVER_CC));
sendMailCmd.setReceiverBCC(getPropAsAddresses(SmtpSampler.RECEIVER_BCC));
sendMailCmd.setReplyTo(getPropAsAddresses(SmtpSampler.MAIL_REPLYTO));
sendMailCmd.setSubject(calculateSubject());
// needed for measuring sending time
sendMailCmd.setSynchronousMode(true);
sendMailCmd.setHeaderFields((CollectionProperty) getProperty(SmtpSampler.HEADER_FIELDS));
return sendMailCmd;
}
private String getRequestHeaders(Message message) throws MessagingException {
StringBuilder sb = new StringBuilder();
@SuppressWarnings("unchecked") // getAllHeaders() is not yet genericised
Enumeration<Header> headers = message.getAllHeaders(); // throws ME
writeHeaders(headers, sb);
return sb.toString();
}
private String getSamplerData(Message message) throws MessagingException, IOException {
StringBuilder sb = new StringBuilder();
Object content = message.getContent(); // throws ME
if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
String contentType = multipart.getContentType();
ContentType ct = new ContentType(contentType);
String boundary = ct.getParameter("boundary");
for (int i = 0; i < multipart.getCount(); i++) { // throws ME
sb.append("--");
sb.append(boundary);
sb.append("\n");
BodyPart bodyPart = multipart.getBodyPart(i); // throws ME
writeBodyPart(sb, bodyPart); // throws IOE, ME
}
sb.append("--");
sb.append(boundary);
sb.append("--");
sb.append("\n");
} else if (content instanceof BodyPart) {
BodyPart bodyPart = (BodyPart) content;
writeBodyPart(sb, bodyPart); // throws IOE, ME
} else if (content instanceof String) {
sb.append(content);
} else {
sb.append("Content has class: ");
sb.append(content.getClass().getCanonicalName());
}
return sb.toString();
}
private void writeHeaders(Enumeration<Header> headers, StringBuilder sb) {
while (headers.hasMoreElements()) {
Header header = headers.nextElement();
sb.append(header.getName());
sb.append(": ");
sb.append(header.getValue());
sb.append("\n");
}
}
private void writeBodyPart(StringBuilder sb, BodyPart bodyPart)
throws MessagingException, IOException {
@SuppressWarnings("unchecked") // API not yet generic
Enumeration<Header> allHeaders = bodyPart.getAllHeaders(); // throws ME
writeHeaders(allHeaders, sb);
String disposition = bodyPart.getDisposition(); // throws ME
sb.append("\n");
if (Part.ATTACHMENT.equals(disposition)) {
sb.append("<attachment content not shown>");
} else {
sb.append(bodyPart.getContent()); // throws IOE, ME
}
sb.append("\n");
}
/**
* Get the list of addresses or null.
* Null is treated differently from an empty list.
*
* @param propKey key of the property containing addresses separated by ";"
* @return the list or null if the input was the empty string
* @throws AddressException thrown if any address is an illegal format
*/
private List<InternetAddress> getPropAsAddresses(String propKey) throws AddressException {
final String propValue = getPropertyAsString(propKey).trim();
if (!propValue.isEmpty()) { // we have at least one potential address
List<InternetAddress> addresses = new ArrayList<>();
for (String address : propValue.split(";")) {
addresses.add(new InternetAddress(address.trim()));
}
return addresses;
} else {
return null;
}
}
/**
* @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement)
*/
@Override
public boolean applies(ConfigTestElement configElement) {
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
}
}